package com.cicadasmall.redis.aspect;

import com.cicadasmall.redis.annotation.RedisCache;
import com.cicadasmall.redis.annotation.RedisEvict;
import com.cicadasmall.common.constant.Constant;
import com.cicadasmall.common.func.Fn;
import com.cicadasmall.redis.utils.RedisUtils;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * RedisCache Aspect
 *
 * @author Jin
 */
@Component
@Aspect
@Slf4j
public class RedisCacheAspect {


    @Pointcut("@annotation(com.cicadasmall.redis.annotation.RedisCache)")
    public void redisCachePoint() {
    }

    @Pointcut("@annotation(com.cicadasmall.redis.annotation.RedisCache)")
    public void redisEvictPoint() {
    }

    @After("redisEvictPoint()")
    public void evict(JoinPoint point) {
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        RedisEvict redisEvict = method.getAnnotation(RedisEvict.class);
        if (Fn.isNotNull(redisEvict)) {
            // 获取RedisCache注解
            String fieldKey = parseKey(redisEvict.fieldKey(), method, point.getArgs());
            String rk = Constant.PREFIX + "cache:" + redisEvict.key() + ":" + fieldKey;
            log.debug("======切面清除rediskey:{} ======", rk);
            RedisUtils.me().delete(rk);
        }

    }


    @Around("redisCachePoint()")
    public Object WriteReadFromRedis(ProceedingJoinPoint point) {
        try {
            Method method = ((MethodSignature) point.getSignature()).getMethod();
            // 获取RedisCache注解
            RedisCache redisCache = method.getAnnotation(RedisCache.class);
            Class<?> returnType = ((MethodSignature) point.getSignature()).getReturnType();
            if (Fn.isNotNull(redisCache) && redisCache.read()) {
                // 查询操作
                log.debug("======method:{} 进入 redisCache 切面 ======", method.getName());
                String fieldKey = parseKey(redisCache.fieldKey(), method, point.getArgs());
                String rk = Constant.PREFIX + "cache:" + redisCache.key() + ":" + fieldKey;
                String rv = RedisUtils.me().get(rk);
                Object obj = fromJson(rv, returnType);
                if (Fn.isNull(obj)) {
                    // Redis 中不存在，则从数据库中查找，并保存到 Redis
                    log.debug("====== Redis 中不存在该记录，从数据库查找 ======");
                    obj = point.proceed();
                    if (Fn.isNotNull(obj)) {
                        if (redisCache.expired() > 0) {
                            RedisUtils.me().setEx(rk, toJson(obj), redisCache.expired(), TimeUnit.SECONDS);
                        } else {
                            RedisUtils.me().set(rk, toJson(obj));
                        }
                    }
                }
                return obj;
            }
        } catch (Throwable ex) {
            log.error("====== RedisCache 执行异常: {} ======", ex);
        }
        return null;
    }

    /**
     * 获取缓存的key
     * key 定义在注解上，支持SPEL表达式,可以识别下划线拼接的值
     */
    private String parseKey(String key, Method method, Object[] args) {
        String fieldKey = key;
        String[] keys = key.split("_");
        // 获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        if (paraNameArr.length > 0) {
            // 使用SPEL进行key的解析
            ExpressionParser parser = new SpelExpressionParser();
            // SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            // 把方法参数放入SPEL上下文中
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
            for (int i = 0; i < keys.length; i++) {
                fieldKey += "_" + parser.parseExpression(keys[i]).getValue(context, String.class);
            }
            return fieldKey.substring(1);
        }
        return fieldKey;
    }

    private String toJson(Object object) {
        if (object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double
                || object instanceof Boolean || object instanceof String) {
            return String.valueOf(object);
        }
        return new Gson().toJson(object);
    }


    private <T> T fromJson(String json, Class<T> clazz) {
        return new Gson().fromJson(json, clazz);
    }

}